﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;
using ActivateYourGlutes.Controllers;
using ActivateYourGlutes.Data;
using ActivateYourGlutes.Infrastructure.MembershipServices;
using ActivateYourGlutes.Payloads.Account;
using ActivateYourGlutes.Services;
using ActivateYourGlutes.Services.Model;
using ActivateYourGlutes.Tests.Unit.Mocks;
using ActivateYourGlutes.Helpers;
using NUnit.Framework;
using Rhino.Mocks;

namespace ActivateYourGlutes.Tests.Unit.Controllers
{
    [TestFixture]
    public class Test_AccountController : ControllerTestBase
    {
        private const string AccountEmail = "testuser@somedomain.com";
        private const int MinPasswordLength = 6;
        private AccountController _controller;
        private IFormsAuthentication _formsAuth;
        private IAccountProvider _accountProvider;
        private ILookupProvider _lookupProvider;
        private IBodyProvider _bodyProvider;
        private IMeasurementTypeProvider _measurementTypeProvider;

        [SetUp]
        public override void Init()
        {
            base.Init();
            _mockAccount.SecondaryMeasurementType = null;
        }

        [TearDown]
        public override void TearDown()
        {
            base.TearDown();
            _controller = null;
            _lookupProvider = null;
            _formsAuth = null;
            _accountProvider = null;
            _lookupProvider = null;
            _bodyProvider = null;
        }

        #region Controller and dependency creation

        private void CreateStubs()
        {
            _formsAuth = MockRepository.GenerateStub<IFormsAuthentication>();
            _bodyProvider = MockRepository.GenerateStub<IBodyProvider>();
            _lookupProvider = MockRepository.GenerateStub<ILookupProvider>();
            _measurementTypeProvider = MockRepository.GenerateStub<IMeasurementTypeProvider>();
        }

        private void CreateController()
        {
            _accountProvider = MockRepository.GenerateStub<IAccountProvider>();
            _accountProvider.Stub(ap => ap.MinPasswordLength).Return(MinPasswordLength);
            _accountProvider.Stub(ap => ap.CurrentAccount(null)).Return(_mockAccount).IgnoreArguments();
            _accountProvider.Stub(ap => ap.EmailForAccount(null)).IgnoreArguments().Return(AccountEmail);
            MockHttpContext controllerContext = new MockHttpContext();
            _controller = new AccountController(_accountProvider, _bodyProvider, _lookupProvider, _measurementTypeProvider, _formsAuth, _logger);
            _controller.ControllerContext = new ControllerContext(controllerContext, new RouteData(), _controller);
            _controller.LocaleSettings = _localeSettings;
            _controller.Url = new UrlHelper(new RequestContext(controllerContext, new RouteData()));
        }

        private void InitialiseControllerWithMockLookupProvider()
        {
            CreateStubs();
            _lookupProvider = MockRepository.GenerateMock<ILookupProvider>();
            CreateController();
        }

        private void InitialiseControllerWithMockAccountProvider()
        {
            CreateStubs();
            CreateController();
        }

        private void InitialiseControllerWithMockFormsAuthenticator()
        {
            CreateStubs();
            _formsAuth = MockRepository.GenerateMock<IFormsAuthentication>();
            CreateController();
        }

        #endregion

        #region Change password unit tests

        [Test]
        public void Test_ChangePassword_Get_ReturnsView()
        {
            // Arrange
            CreateController();

            // Act
            ViewResult result = (ViewResult)_controller.ChangePassword();

            // Assert
            Assert.That(result.ViewData["PasswordLength"], Is.EqualTo(MinPasswordLength));
        }

        [Test]
        public void Test_ChangePassword_Post_RedirectsOnSuccess()
        {
            // Arrange
            CreateController();
            _accountProvider.Expect(ms => ms.ChangePassword(null, null, null, null)).Return(true).IgnoreArguments();

            // Act
            RedirectToRouteResult result = (RedirectToRouteResult)_controller.ChangePassword("oldPass", "newPass", "newPass");

            // Assert

            Assert.That(result.RouteValues["action"], Is.EqualTo("ChangePasswordSuccess"));
            _accountProvider.VerifyAllExpectations();
        }

        [Test]
        public void Test_ChangePassword_Post_ReturnsViewIfCurrentPasswordNotSpecified()
        {
            // Arrange
            CreateController();
            NameValueCollection errors = new NameValueCollection();
            errors.Add("currentPassword", "You must specify a current password.");
            _accountProvider.Stub(ap => ap.ChangePassword(null, null, null, null))
                .IgnoreArguments().Throw(new ValidationException(errors));
            
            // Act
            ViewResult result = (ViewResult)_controller.ChangePassword("", "newPassword", "newPassword");

            // Assert
            Assert.That(result.ViewData["PasswordLength"], Is.EqualTo(MinPasswordLength));
            Assert.That(result.ViewData.ModelState["currentPassword"].Errors[0].ErrorMessage,
                        Is.EqualTo("You must specify a current password."));
        }

        [Test]
        public void Test_ChangePassword_Post_ReturnsViewIfNewPasswordDoesNotMatchConfirmPassword()
        {
            // Arrange
            CreateController();
            NameValueCollection errors = new NameValueCollection();
            errors.Add("_FORM", "The new password and confirmation password do not match.");
            _accountProvider.Stub(ap => ap.ChangePassword(null, null, null, null))
                .IgnoreArguments().Throw(new ValidationException(errors));

            // Act
            ViewResult result = (ViewResult)_controller.ChangePassword("currentPassword", "newPassword", "otherPassword");

            // Assert
            Assert.That(result.ViewData["PasswordLength"], Is.EqualTo(MinPasswordLength));
            Assert.That(result.ViewData.ModelState["_FORM"].Errors[0].ErrorMessage, Is.EqualTo("The new password and confirmation password do not match."));
        }

        [Test]
        public void Test_ChangePassword_Post_MembershipServiceThrowsException()
        {
            // Arrange
            CreateController();
            _accountProvider.Expect(ap => ap.ChangePassword(null, null, null, null)).Throw(new MockException()).IgnoreArguments();

            // Act
            ViewResult result = (ViewResult)_controller.ChangePassword("oldPass", "newPass", "newPass");

            // Assert
            Assert.That(result.ViewData["PasswordLength"], Is.EqualTo(MinPasswordLength));
            Assert.That(result.ViewData.ModelState["_FORM"].Errors[0].ErrorMessage, Is.EqualTo("The current password is incorrect or the new password is invalid."));
        }

        [Test]
        public void Test_ChangePassword_Post_MembershipServiceReturnsFalse()
        {
            // Arrange
            CreateController();
            _accountProvider.Expect(ms => ms.ChangePassword(null, null, null, null)).Return(false).IgnoreArguments();

            // Act
            ViewResult result = (ViewResult)_controller.ChangePassword("oldPass", "newPass", "newPass");

            // Assert
            Assert.That(result.ViewData["PasswordLength"], Is.EqualTo(MinPasswordLength));
            Assert.That(result.ViewData.ModelState["_FORM"].Errors[0].ErrorMessage, Is.EqualTo("The current password is incorrect or the new password is invalid."));
        }

        [Test]
        public void Test_ChangePassword_Get_Success()
        {
            // Arrange
            CreateController();

            // Act
            ViewResult result = (ViewResult) _controller.ChangePasswordSuccess();

            // Assert
            // Nothing to do. Test included for completeness.
        }

        #endregion

        #region Registration unit tests

        private IEnumerable<UnitOfWeight> GetMockUnitOfWeightList()
        {
            return new List<UnitOfWeight>()
                       {
                           _kilogrammes,
                           new UnitOfWeight() {Description = "lbs"}
                       };
        }

        private IEnumerable<UnitOfLength> GetMockUnitOfLengthList()
        {
            return new List<UnitOfLength>()
                       {
                           _centimeters,
                           new UnitOfLength() {Description = "inches"}
                       };
        }

        private void SetupLookupStubs()
        {
            _lookupProvider.Stub(lp => lp.UnitsOfLength).Return(GetMockUnitOfLengthList());
            _lookupProvider.Stub(lp => lp.UnitsOfWeight).Return(GetMockUnitOfWeightList());
            _measurementTypeProvider.Stub(bp => bp.MeasurementTypes(null)).IgnoreArguments().Return(_measurementTypes);
        }

        private void Assert_RegisterViewData(ViewResult result)
        {
            var payload = (RegisterPayload)result.ViewData.Model;
            Assert.That(payload.UnitsOfLength.First().Text, Is.EqualTo("cm"));
            Assert.That(payload.UnitsOfLength.Count(), Is.EqualTo(2));
            Assert.That(payload.UnitsOfWeight.First().Text, Is.EqualTo("kg"));
            Assert.That(payload.UnitsOfWeight.Count(), Is.EqualTo(2));
            Assert.That(payload.MinimumPasswordLength, Is.EqualTo(MinPasswordLength));
        }

        [Test]
        public void Test_Register_Get_ReturnsView()
        {
            // Arrange
            IEnumerable<UnitOfLength> mockUnitOfLengthList = GetMockUnitOfLengthList();
            IEnumerable<UnitOfWeight> mockUnitOfWeightList = GetMockUnitOfWeightList();

            InitialiseControllerWithMockLookupProvider();
            _lookupProvider.Expect(lp => lp.UnitsOfLength).Return(mockUnitOfLengthList);
            _lookupProvider.Expect(lp => lp.UnitsOfWeight).Return(mockUnitOfWeightList);

            // Act
            ViewResult result = (ViewResult) _controller.Register();

            // Assert
            Assert_RegisterViewData(result);
            _lookupProvider.VerifyAllExpectations();
        }

        [Test]
        public void Test_Register_Post_RedirectsToHomePage()
        {
            // Arrange
            InitialiseControllerWithMockLookupProvider();
            SetupLookupStubs();
            DateTime dateOfBirth18 = DateTime.Now.AddYears(-18);

            // Act
            RedirectResult result =
                (RedirectResult)
                _controller.Register(dateOfBirth18.ToString("MM-dd-yyyy"), "month-day-year", "testuser", "testemail@mailbox.com", "password",
                                     "password", 0, 0, true);


            // Assert
            Assert.That(result.Url, Is.EqualTo(MockHttpContext.Url));
        }

        [Test]
        public void Test_Register_Post_ValidationErrorReturnsView()
        {
            // Arrange
            InitialiseControllerWithMockLookupProvider();
            SetupLookupStubs();
            _accountProvider.Stub(ap => ap.Create(null, null, null, DateTime.Now, null, null, null))
                .IgnoreArguments()
                .Throw(new ValidationException("_FORM", "Whoops"));

            // Act
            ViewResult result =
                (ViewResult)
                _controller.Register(DateTime.Now.ToString("MM-dd-yyyy"), "month-day-year", "testuser", "testemail@mailbox.com", "password",
                                     "password", 0, 0, true);


            // Assert
            Assert.That(result.ViewData.ModelState["_FORM"].Errors[0].ErrorMessage, Is.EqualTo("Whoops"));
        }

        

        #endregion

        #region Tests for the index (account details)

        private void Assert_IndexViewData(ViewResult result)
        {
            var payload = (IndexPayload) result.ViewData.Model;
            // Lookups
            Assert.That(payload.UnitsOfLength.First().Text, Is.EqualTo("cm"));
            Assert.That(payload.UnitsOfLength.Count(), Is.EqualTo(2));
            Assert.That(payload.UnitsOfWeight.First().Text, Is.EqualTo("kg"));
            Assert.That(payload.UnitsOfWeight.Count(), Is.EqualTo(2));

            // The rest
            Assert.That(payload.Email, Is.EqualTo(AccountEmail));
            Assert.That(payload.Account, Is.EqualTo(_mockAccount));
        }

        [Test]
        public void Test_Index_Get_ReturnsView_SingleCheckedKeyIndicator()
        {
            // Arrange
            InitialiseControllerWithMockAccountProvider();
            SetupLookupStubs();
            
            // Act
            ViewResult result = (ViewResult)_controller.Index();

            // Assert
            var payload = (IndexPayload) result.ViewData.Model;
            Assert_IndexViewData(result);
            Assert.That(payload.Success, Is.Null);
            Assert.That(payload.HorizontalMeasurementTypesPayload.Error, Is.Null);
            Assert.That(payload.HorizontalMeasurementTypesPayload.MeasurementTypes, Is.EqualTo(_measurementTypes));
            Assert.That(payload.HorizontalMeasurementTypesPayload.SelectedTypes.Count(), Is.EqualTo(1));
            Assert.That(payload.HorizontalMeasurementTypesPayload.SelectedTypes.First(), Is.EqualTo(_weight));
        }

        [Test]
        public void Test_Index_Get_ReturnsView_DoubleCheckedKeyIndicator()
        {
            // Arrange
            InitialiseControllerWithMockAccountProvider();
            SetupLookupStubs();
            _mockAccount.SecondaryMeasurementType = _bodyFat;

            // Act
            ViewResult result = (ViewResult)_controller.Index();

            // Assert
            Assert_IndexViewData(result);
            var payload = (IndexPayload)result.ViewData.Model;
            Assert.That(payload.Success, Is.Null);
            Assert.That(payload.HorizontalMeasurementTypesPayload.Error, Is.Null);
            Assert.That(payload.HorizontalMeasurementTypesPayload.MeasurementTypes, Is.EqualTo(_measurementTypes));
            Assert.That(payload.HorizontalMeasurementTypesPayload.SelectedTypes.Count(), Is.EqualTo(2));
            Assert.That(payload.HorizontalMeasurementTypesPayload.SelectedTypes.First(), Is.EqualTo(_weight));
            Assert.That(payload.HorizontalMeasurementTypesPayload.SelectedTypes.Skip(1).First(), Is.EqualTo(_bodyFat));
        }

        [Test]
        public void Test_Index_Post_SuccessfulSave()
        {
            // Arrange
            InitialiseControllerWithMockAccountProvider();
            SetupLookupStubs();
            _accountProvider.Expect(ap => ap.UpdateAccount(null, null, DateTime.Now, null, null, null)).IgnoreArguments();
            FormCollection postValues = new FormCollection()
                                            {
                                                { "Email", AccountEmail },
                                                { _weight.ToFormName(), "true" },
                                                { "UnitForLengthId", "0" },
                                                { "UnitForWeightId", "0" },
                                                { "DateOfBirth", "01-01-1990"}
                                            };

            // Act
            ViewResult result = (ViewResult)_controller.Index(postValues);

            // Assert
            Assert_IndexViewData(result);
            var payload = (IndexPayload)result.ViewData.Model;
            Assert.That(payload.Success, Is.EqualTo("Your account has been updated"));
            Assert.That(payload.HorizontalMeasurementTypesPayload.Error, Is.Null);
            Assert.That(payload.HorizontalMeasurementTypesPayload.MeasurementTypes, Is.EqualTo(_measurementTypes));
            Assert.That(payload.HorizontalMeasurementTypesPayload.SelectedTypes.Count(), Is.EqualTo(1));
            Assert.That(payload.HorizontalMeasurementTypesPayload.SelectedTypes.First(), Is.EqualTo(_weight));

            _accountProvider.VerifyAllExpectations();
        }

        [Test]
        public void Test_Index_Post_SuccessfulSaveWithTwoIndicators()
        {
            // Arrange
            InitialiseControllerWithMockAccountProvider();
            SetupLookupStubs();
            _accountProvider.Expect(ap => ap.UpdateAccount(null, null, DateTime.Now, null, null, null)).IgnoreArguments();
            FormCollection postValues = new FormCollection()
                                            {
                                                { "Email", AccountEmail },
                                                { _weight.ToFormName(), "true" },
                                                { _bodyFat.ToFormName(), "true" },
                                                { "UnitForLengthId", "0" },
                                                { "UnitForWeightId", "0" },
                                                { "DateOfBirth", "01-01-1990"}
                                            };

            // Act
            ViewResult result = (ViewResult)_controller.Index(postValues);

            // Assert
            Assert_IndexViewData(result);
            var payload = (IndexPayload)result.ViewData.Model;
            Assert.That(payload.Success, Is.EqualTo("Your account has been updated"));
            Assert.That(payload.HorizontalMeasurementTypesPayload.Error, Is.Null);
            Assert.That(payload.HorizontalMeasurementTypesPayload.MeasurementTypes, Is.EqualTo(_measurementTypes));
            Assert.That(payload.HorizontalMeasurementTypesPayload.SelectedTypes.Count(), Is.EqualTo(2));
            Assert.That(payload.HorizontalMeasurementTypesPayload.SelectedTypes.First(), Is.EqualTo(_weight));
            Assert.That(payload.HorizontalMeasurementTypesPayload.SelectedTypes.Skip(1).First(), Is.EqualTo(_bodyFat));

            _accountProvider.VerifyAllExpectations();
        }

        [Test]
        public void Test_Index_Post_ThreeIndicatorsGiveError()
        {
            // Arrange
            InitialiseControllerWithMockAccountProvider();
            SetupLookupStubs();
            _accountProvider.AssertWasNotCalled(ap => ap.UpdateAccount(Arg<Account>.Is.Anything,
                Arg<string>.Is.Anything,
                Arg<DateTime>.Is.Anything,
                Arg<UnitOfLength>.Is.Anything,
                Arg<UnitOfWeight>.Is.Anything, Arg<IEnumerable<MeasurementType>>.Is.Anything));
            FormCollection postValues = new FormCollection()
                                            {
                                                { "Email", AccountEmail },
                                                { _weight.ToFormName(), "true" },
                                                { _bodyFat.ToFormName(), "true" },
                                                { _chest.ToFormName(), "true" },
                                                { "UnitForLengthId", "0" },
                                                { "UnitForWeightId", "0" },
                                                { "DateOfBirth", "01-01-1990"}
                                            };

            // Act
            ViewResult result = (ViewResult)_controller.Index(postValues);

            // Assert
            Assert_IndexViewData(result);

            var payload = (IndexPayload)result.ViewData.Model;
            Assert.That(payload.Success, Is.Null);
            Assert.That(payload.HorizontalMeasurementTypesPayload.Error, Is.EqualTo("You can select upto two items"));
            Assert.That(payload.HorizontalMeasurementTypesPayload.MeasurementTypes, Is.EqualTo(_measurementTypes));
            Assert.That(payload.HorizontalMeasurementTypesPayload.SelectedTypes.Count(), Is.EqualTo(3));
            Assert.That(payload.HorizontalMeasurementTypesPayload.SelectedTypes.First(), Is.EqualTo(_weight));
            Assert.That(payload.HorizontalMeasurementTypesPayload.SelectedTypes.Skip(1).First(), Is.EqualTo(_bodyFat));
            Assert.That(payload.HorizontalMeasurementTypesPayload.SelectedTypes.Skip(2).First(), Is.EqualTo(_chest));

            _accountProvider.VerifyAllExpectations();
        }

        [Test]
        public void Test_Index_Post_ModelExceptionAddedToErrors()
        {
            const string emailErrorMessage = "Crazy Email Address";
            const string emailErrorKey = "email";

            // Arrange
            InitialiseControllerWithMockAccountProvider();
            SetupLookupStubs();
            _accountProvider.Expect(ap => ap.UpdateAccount(null, null, DateTime.Now, null, null, null))
                .Throw(new ValidationException(new NameValueCollection() { { emailErrorKey, emailErrorMessage}}))
                .IgnoreArguments();
            FormCollection postValues = new FormCollection()
                                            {
                                                { "Email", "CrazyEmail" },
                                                { _weight.ToFormName(), "true" },
                                                { "UnitForLengthId", "0" },
                                                { "UnitForWeightId", "0" },
                                                { "DateOfBirth", "01-01-1990"}
                                            };

            // Act
            ViewResult result = (ViewResult)_controller.Index(postValues);

            // Assert
            Assert_IndexViewData(result);
            var payload = (IndexPayload)result.ViewData.Model;
            Assert.That(payload.Success, Is.Null);
            Assert.That(payload.HorizontalMeasurementTypesPayload.Error, Is.Null);
            Assert.That(payload.HorizontalMeasurementTypesPayload.MeasurementTypes, Is.EqualTo(_measurementTypes));
            Assert.That(payload.HorizontalMeasurementTypesPayload.SelectedTypes.Count(), Is.EqualTo(1));
            Assert.That(payload.HorizontalMeasurementTypesPayload.SelectedTypes.First(), Is.EqualTo(_weight));
            Assert.That(result.ViewData.ModelState[emailErrorKey].Errors[0].ErrorMessage, Is.EqualTo(emailErrorMessage));

            _accountProvider.VerifyAllExpectations();
        }

        #endregion

        #region Terms and conditions and privacy

        [Test]
        public void Test_TermsAndConditions_Get_ReturnsView()
        {
            // Arrange
            CreateController();

            // Act
            ViewResult result = (ViewResult)_controller.TermsAndConditions();

            // Assert
            // Nothing to assert
        }

        [Test]
        public void Test_Privacy_Get_ReturnsView()
        {
            // Arrange
            CreateController();

            // Act
            ViewResult result = (ViewResult)_controller.Privacy();

            // Assert
            // Nothing to assert
        }

        #endregion

        #region Logon

        [Test]
        public void Test_Logon_Get_ReturnsView()
        {
            // Arrange
            InitialiseControllerWithMockFormsAuthenticator();

            // Act
            ViewResult result = (ViewResult) _controller.LogOn();

            // Assert
            // Nothing to assert
        }

        [Test]
        public void Test_Logon_Post_RedirectsToHomepage()
        {
            // Arrange
            InitialiseControllerWithMockFormsAuthenticator();
            _formsAuth.Expect(fa => fa.SignIn(null, true)).IgnoreArguments();

            // Act
            RedirectResult result = (RedirectResult) _controller.LogOn("testuser", "password", true, null);

            // Assert
            Assert.That(result.Url, Is.EqualTo(MockHttpContext.Url));
            _formsAuth.VerifyAllExpectations();
        }

        [Test]
        public void Test_Logon_Post_RedirectsToGivenUrl()
        {
            // Arrange
            const string dummyUrl = "http://someurl.com";
            InitialiseControllerWithMockFormsAuthenticator();
            _formsAuth.Expect(fa => fa.SignIn(null, true)).IgnoreArguments();

            // Act
            RedirectResult result = (RedirectResult)_controller.LogOn("testuser", "password", true, dummyUrl);

            // Assert
            Assert.That(result.Url, Is.EqualTo(dummyUrl));
            _formsAuth.VerifyAllExpectations();
        }

        [Test]
        public void Test_Logon_Post_ValidationErrorReturnsView()
        {
            // Arrange
            InitialiseControllerWithMockFormsAuthenticator();
            NameValueCollection errors = new NameValueCollection();
            errors.Add("_FORM", "The username or password provided is incorrect.");
            _accountProvider.Stub(ap => ap.ValidateLogOn(null, null))
                .IgnoreArguments().Throw(new ValidationException(errors));
            _formsAuth.AssertWasNotCalled(fa => fa.SignIn(Arg<string>.Is.Anything, Arg<bool>.Is.Anything));

            // Act
            ViewResult result = (ViewResult)_controller.LogOn("testuser", "password", true, null);

            // Assert
            Assert.That(result.ViewData.ModelState["_FORM"].Errors[0].ErrorMessage, Is.EqualTo("The username or password provided is incorrect."));
            _formsAuth.VerifyAllExpectations();
        }

        #endregion

        #region Logoff

        [Test]
        public void Test_Logoff_Get_RedirectsToHomepage()
        {
            // Arrange
            InitialiseControllerWithMockFormsAuthenticator();
            _formsAuth.Expect(fa => fa.SignOut());

            // Act
            RedirectToRouteResult result = (RedirectToRouteResult) _controller.LogOff();

            // Assert
            Assert.That(result.RouteValues["action"], Is.EqualTo("Index"));
            Assert.That(result.RouteValues["controller"], Is.EqualTo("Home"));
            
            _formsAuth.VerifyAllExpectations();
        }

        #endregion
    }
}